package com.ruoyi.web.controller.me;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ProcessStopException;
import com.ruoyi.common.utils.ExecUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SseEmitterUtils;
import com.ruoyi.common.utils.biz.BizModule;
import com.ruoyi.common.utils.biz.CommonUtil;
import com.ruoyi.common.utils.biz.PathUtil;
import com.ruoyi.common.utils.biz.ResultSaveModule;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.web.server.entity.ParamFileSave;
import com.ruoyi.web.server.service.ParamFileSaveService;
import com.ruoyi.web.vo.DifferentMagneticVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.File;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 *  不同地磁条件模型评估
 * </p>
 *
 * @author liuhl
 * @since 2024-05-13
 */
@RestController
@RequestMapping("/me/differentMag")
@AllArgsConstructor
@Slf4j
@Api(tags = "模型检验评估-不同地磁条件模型评估")
public class DifferentMagController implements CommandLineRunner {

    private ParamFileSaveService paramFileSaveService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Scheduled(cron = "0 0 3 * * *")
    public void scheduleTask() {
        List<String> magneticLevel = ListUtil.of("period_of_quiet_magnetic_storm","period_of_weak_magnetic_storm","periods_of_severe_magnetic_storms");
        //因 /scene 接口过于慢,因此 每天异步调用一次
        ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
        executor.execute(() -> {
            // 2000 年 至现在,每年调用一次
            int year = LocalDate.now().getYear();
            for (int time = 2000; time <= year; time++) {
                for (String level : magneticLevel) {
                    //{"time":"2022","magneticLevel":"period_of_weak_magnetic_storm"}
                    String key = "SCENE:"+time+"_"+level;
                    String codePath = PathUtil.codeModuleDir(BizModule.MODEL_ASSESS) + PathUtil.moduleDirs().getModelAssess().get("scene");
                    String tmpDir = PathUtil.tmpModuleDir(BizModule.MODEL_ASSESS);
                    String command = "python " + codePath + " " + time + " " + level + " " + tmpDir;
                    String results = RuntimeUtil.execForStr(command);
                    if (results != null && results.contains("###")) {
                        String txtPath = results.split("###")[1];
                        String content = CommonUtil.readStrData(new File(txtPath), null);
                        redisTemplate.opsForValue().set(key, content, 24, TimeUnit.HOURS);
                    }
                }
            }
            log.info("缓存情景下拉成功");
        });
    }


    @PostMapping("/scene")
    @ApiOperation(value = "情景下拉", notes = "根据日期和地磁条件获取情景列表", httpMethod = "POST")
    public AjaxResult getScene(@RequestBody DifferentMagneticVo vo) {

        String key = "SCENE:"+vo.getTime()+"_"+vo.getMagneticLevel();
        String content = (String) redisTemplate.opsForValue().get(key);
        if (null == content) {
            String codePath = PathUtil.codeModuleDir(BizModule.MODEL_ASSESS) + PathUtil.moduleDirs().getModelAssess().get("scene");
            String tmpDir = PathUtil.tmpModuleDir(BizModule.MODEL_ASSESS);

            String command = "python " + codePath + " " + vo.getTime() + " " + vo.getMagneticLevel() + " " + tmpDir;
            String results = ExecUtil.execCmdWithResult(command);
            if (results == null || !results.contains("###")) {
                return AjaxResult.error(results);
            }
            String txtPath = results.split("###")[1];
            content = CommonUtil.readStrData(new File(txtPath), null);
            redisTemplate.opsForValue().set(key, content, 24, TimeUnit.HOURS);
        }
        Map<String,Object> mapResult = JSONUtil.toBean(content, Map.class);
        List<String> resultList = mapResult.entrySet().stream().map(entry -> {
            return entry.getKey() + ":" + entry.getValue();
        }).collect(Collectors.toList());
        resultList = resultList.stream().sorted((a,b) -> {
            return DateUtil.compare(DateUtil.parse(a.split("~|:")[0],"yyyy-MM-dd"),DateUtil.parse(b.split("~|:")[0],"yyyy-MM-dd"));}).collect(Collectors.toList());
        return AjaxResult.success(resultList);
    }

    @PostMapping("/ap")
    @ApiOperation(value = "获取ap指数分布图", notes = "根据情景获取获取ap指数分布图", httpMethod = "POST")
    public AjaxResult getAp(@RequestBody DifferentMagneticVo vo) {

        String codePath = PathUtil.codeModuleDir(BizModule.MODEL_ASSESS)  + PathUtil.moduleDirs().getModelAssess().get("apValue");
        String tmpDir = PathUtil.tmpModuleDir(BizModule.MODEL_ASSESS);

        String command = "python "+codePath+" "+vo.getScene()+" "+tmpDir;
        String results = ExecUtil.execCmdWithResult(command);
        if (results == null || !results.contains("###")){
            return AjaxResult.error(results);
        }
        String txtPath = results.split("###")[1];
        String content = CommonUtil.readStrData(new File(txtPath), null);
        return AjaxResult.success("",content);
    }

    @PostMapping("/save")
    @ApiOperation(value = "结果保存", notes = "保存页面参数和结果文件", httpMethod = "POST")
    public AjaxResult saveResult(@RequestBody DifferentMagneticVo vo) {

        //1.参数整理
        String magneticLevel = vo.getMagneticLevel(); //period_of_weak_magnetic_storm
        String scene = vo.getScene(); //2014-01-01~2014-01-10:20
        String sateName = vo.getSateName();//SWDATA_OBS_DEN_SWARMA
        String model = vo.getModel(); //JB2008,nrlmsise00
        String txtPath = vo.getTxtPath();
        if (StrUtil.isBlank(magneticLevel) || StrUtil.isBlank(scene) || StrUtil.isBlank(sateName)
                || StrUtil.isBlank(model) || StrUtil.isBlank(txtPath)){
            return AjaxResult.error("入参有空值");
        }

        String delimiter = "____"; //4个下划线
        String params = new StringBuilder()
                .append(magneticLevel).append(delimiter)
                .append(scene).append(delimiter)
                .append(sateName).append(delimiter)
                .append(model).toString();

        ParamFileSave dbEntity = paramFileSaveService.getOne(new LambdaQueryWrapper<ParamFileSave>().eq(ParamFileSave::getParams, params));
        if (dbEntity != null){
            return AjaxResult.success();
        }

        //2.临时文件 运输到 永久空间
        File file = new File(txtPath);
        if (!file.exists()){
            return AjaxResult.error("计算结果文件已不存在");
        }
        String outputPath = txtPath.replace(PathUtil.tmpDir(), PathUtil.outputDir());
        try {
            FileChannel from = FileChannel.open(file.toPath(), StandardOpenOption.READ);
            File toFile = new File(outputPath);
            if (toFile.exists()){
                toFile.delete();
            }
            FileChannel to = FileChannel.open(toFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);
            from.transferTo(0, from.size(), to);
            from.close();
            to.close();
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }finally {
            if (file.exists()){
                file.delete();
            }
        }

        //3.保存

        ParamFileSave saveEntity = ParamFileSave.builder()
                .params(params)
                .filePath(outputPath)
                .createTime(new Date())
                .build();
        paramFileSaveService.save(saveEntity);

        return AjaxResult.success();
    }


    @PostMapping("/cal")
    @ApiOperation(value = "计算评估", notes = "根据地磁条件、情景、卫星、模型进行计算评估", httpMethod = "POST")
    public AjaxResult calculate(@RequestBody DifferentMagneticVo vo) throws ProcessStopException {

        String codePath = PathUtil.codeModuleDir(BizModule.MODEL_ASSESS)  + PathUtil.moduleDirs().getModelAssess().get("cal");
        String tmpDir = PathUtil.tmpModuleDir(BizModule.MODEL_ASSESS);

        String delimiter = "____"; //4个下划线
        String params = new StringBuilder()
                .append(vo.getMagneticLevel()).append(delimiter)
                .append(vo.getScene()).append(delimiter)
                .append(vo.getSateName()).append(delimiter)
                .append(vo.getModel()).toString();

        ParamFileSave dbEntity = paramFileSaveService.getOne(new LambdaQueryWrapper<ParamFileSave>().eq(ParamFileSave::getParams, params));
        String txtPath;
        if (dbEntity != null){
            txtPath = dbEntity.getFilePath();
        }else {
            List<String> command = Stream.of("python",
                    "-u",
                    codePath,
                    vo.getMagneticLevel(),
                    vo.getScene(),
                    vo.getSateName(),
                    vo.getModel(),
                    tmpDir
            ).collect(Collectors.toList());
            String sseId = vo.getSseId()+SecurityUtils.getLoginUser().getUsername();
            String results = ExecUtil.execPython(command, sseId);
            SseEmitterUtils.sendMessage(sseId, "100");
            SseEmitterUtils.remove(sseId);
            if (results == null || !results.contains("###")){
                return AjaxResult.error(results);
            }
            txtPath = results.split("###")[1];
        }

        String content = CommonUtil.readStrData(new File(txtPath), null);
        String[] dbTableName = vo.getSateName().split("_");
        String sateName = dbTableName[dbTableName.length-1];
        Map<String, JSONObject> contentMap = JSONUtil.toBean(content.replaceAll(sateName+"#",""), Map.class);


        Map<String,Object> resultMap = new HashMap<>();

        //1. modelData 观测值和参评模型模拟值分布结果图

        Map<String,Object> simuMap = new HashMap<>();

        JSONObject modelData = contentMap.get("modelData");
        List<String> obsList = (List<String>) modelData.get(sateName);
        List<String> timeList = (List<String>) modelData.get(sateName + "_time");
        JSONObject simuObj = (JSONObject) modelData.get(sateName + "_simu");

        simuMap.put("time", timeList);
        simuMap.put("obs", obsList);
        simuMap.put("model", simuObj);

        resultMap.put("simu", simuMap);

        //2. errorData 大气模型误差评估结果

        JSONObject errorData = contentMap.get("errorData");

        // 先把杂乱的数据规整，再构建前端需要的数据结构
        // 例如：【JB2008#absolute_error#confidenceInterval】 则map从外到内的key分别是：2#1#3
        Map<String,Map<String,Map<String,Object>>> errorDataAllMap = new HashMap<>();
        Map<String,Object> coeeMap = new HashMap<>();

        errorData.entrySet().forEach( entry -> {
            String key = entry.getKey();
            String[] mapKeyArr = key.split("#");
            if (mapKeyArr.length == 2){ //JB2008#corrCoee 相关系数是2层，所以就一个简单MAP
                coeeMap.put(mapKeyArr[0], Stream.of(entry.getValue()).collect(Collectors.toList())); //前端希望是个数组
                return;
            }
            Map<String, Map<String, Object>> modelMap = errorDataAllMap.get(mapKeyArr[1]);
            if (CollUtil.isEmpty(modelMap)){
                modelMap = new HashMap<>();
                Map<String, Object> dataMap = new HashMap<>();
                dataMap.put(mapKeyArr[2].trim().replace("：",""), entry.getValue());
                modelMap.put(mapKeyArr[0], dataMap);
                errorDataAllMap.put(mapKeyArr[1], modelMap);
            }else {
                Map<String, Object> dataMap = modelMap.get(mapKeyArr[0]);
                if (CollUtil.isEmpty(dataMap)){
                    dataMap = new HashMap<>();
                }
                dataMap.put(mapKeyArr[2].trim().replace("：",""), entry.getValue());
                modelMap.put(mapKeyArr[0], dataMap);
                errorDataAllMap.put(mapKeyArr[1], modelMap);
            }
        });

        //开始构建
        //2.1 大气模型误差评估结果统计图(绝对误差,相对误差)
        Map<String,Object> absoluteRelativeMap = new HashMap<>();
        Map<String,Object> absoluteMap = new HashMap<>();
        Map<String,Object> relativeMap = new HashMap<>();
        errorDataAllMap.get("absolute_error").forEach( (key,valueMap)-> {
            Object value = valueMap.get("result");
            absoluteMap.put(key,value);
        });
        errorDataAllMap.get("relative_error").forEach( (key,valueMap)-> {
            Object value = valueMap.get("result");
            relativeMap.put(key,value);
        });

        absoluteRelativeMap.put("absolute",absoluteMap);
        absoluteRelativeMap.put("relative",relativeMap);
        absoluteRelativeMap.put("time", timeList);
        resultMap.put("absolute_relative" , absoluteRelativeMap);

        //2.2 大气模型误差评估结果统计图 (平均绝对误差、平均相对误差、系数、均方根)

        Map<String,Object> errorDataMap = new HashMap<>();

        Map<String,Object> avgAbsoluteMap = new HashMap<>();
        Map<String,Object> avgRelativeMap = new HashMap<>();
        Map<String,Object> rootMap = new HashMap<>();

        errorDataAllMap.get("absolute_error").forEach( (key,valueMap)-> {
            Object value = valueMap.get("averageResult");
            avgAbsoluteMap.put(key,Stream.of(value).collect(Collectors.toList()));
        });
        errorDataAllMap.get("relative_error").forEach( (key,valueMap)-> {
            Object value = valueMap.get("averageResult");
            avgRelativeMap.put(key,Stream.of(value).collect(Collectors.toList()));
        });
        errorDataAllMap.get("root_mean_square_error").forEach( (key,valueMap)-> {
            Object value = valueMap.get("result");
            rootMap.put(key,Stream.of(value).collect(Collectors.toList()));
        });

        errorDataMap.put("avg_absolute", avgAbsoluteMap);
        errorDataMap.put("avg_relative", avgRelativeMap);
        errorDataMap.put("coee", coeeMap);
        errorDataMap.put("root", rootMap);
        resultMap.put("error_data", errorDataMap);

        //2.3 大气模型误差评估结果 table

        List<Map<String, Object>> tableData = Arrays.asList(vo.getModel().split(","))
                .stream()
                .map(o -> {
                    return MapUtil.builder(new HashMap<String,Object>())
                            .put("sate", sateName)
                            .put("model", o)
                            .put("corrCoee", ((List)coeeMap.get(o)).get(0))
                            .put("root", ((List)rootMap.get(o)).get(0))
                            .put("avgAbsolute", errorDataAllMap.get("absolute_error").get(o).get("confidenceInterval"))
                            .put("avgRelative", errorDataAllMap.get("relative_error").get(o).get("confidenceInterval"))
                            .build();
                }).collect(Collectors.toList());
        resultMap.put("table", tableData);

        //3. 最佳策略结果, 例如：【强磁暴期】最优模型为: 【JB2008】
        JSONArray bestModelStrategy = new JSONArray(contentMap.get("bestModelStrategy"));
        String bestModel = bestModelStrategy.get(0).toString().split("#")[3];
        String magneticLevel = vo.getMagLevelName(vo.getMagneticLevel());
        String bestStrategy = magneticLevel + "最优模型为: " + bestModel;
        resultMap.put("result", bestStrategy);
        resultMap.put("magneticLevel", magneticLevel);
        resultMap.put("sateName", sateName);

        //4. txt文件也要加上
        List<String> list = Stream.of(txtPath).collect(Collectors.toList());
        resultMap.put("resultData", JSONUtil.toJsonStr(list));
        resultMap.put("module", ResultSaveModule.MODEL_ASSESS);
//        resultMap.put("submodule", ResultSaveModule.MODEL_ASSESS_MAG);
        return AjaxResult.success(resultMap);
    }


    @ApiOperation(value = "建立连接", notes = "建立连接")
    @GetMapping(path = "subscribe", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter push(String id, String flag) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return SseEmitterUtils.connect(id+loginUser.getUsername(), flag);
    }

    @Override
    public void run(String... args) throws Exception {
        try {
            scheduleTask();
        }catch (Exception e) {
            log.error("情景下拉缓存报错",e);
        }
    }
}
